package br.gov.cemaden.cliente.precipitacao;

import br.gov.cemaden.cliente.precipitacao.exception.ObjectNotFoundException;
import br.gov.cemaden.cliente.precipitacao.exception.PropertiesException;
import br.gov.cemaden.ftphandle.FTPClient;
import br.gov.cemaden.ftphandle.FTPData;
import br.gov.cemaden.library.dao.previsao.AquisicaoPrecipitacaoDAO;
import br.gov.cemaden.library.dao.previsao.PrevisaoPrecipitacaoDAO;
import br.gov.cemaden.library.model.alerta.Agencia;
import br.gov.cemaden.library.model.previsao.AquisicaoPrecipitacao;
import br.gov.cemaden.library.model.previsao.PrevisaoPrecipitacao;
import br.gov.cemaden.library.repository.alerta.AgenciaRepository;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.SftpException;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

/**
 * Hello world!
 *
 */
public class Cliente 
{
    
    private final Logger logger;
    private final Marker FATAL;
    
    private static final Comparator<Path> DATE_ORDER = new Comparator<Path>() {

        @Override
        public int compare(Path p1, Path p2) {
            Integer x = null, y = null;
            Matcher m;
            m = Pattern.compile("\\d+").matcher(p1.getFileName().toString());
            while(m.find())
            {
                x = Integer.parseInt(m.group());
            }
            m = Pattern.compile("\\d+").matcher(p2.getFileName().toString());
            while(m.find())
            {
                y = Integer.parseInt(m.group());
            }
            
            if(x != null && y!= null)
            {
                return x.compareTo(y);
            }
            return 0;
        }
    };
    
    public Cliente()
    {        
        this.logger = LoggerFactory.getLogger(Cliente.class);
        this.FATAL = MarkerFactory.getMarker("FATAL");
    }

    public static void main(String[] args) {

        Cliente cliente = new Cliente();
        
        Logger logger = cliente.logger;
        Marker FATAL = cliente.FATAL;   
        
        List<String> methods = new ArrayList(2);
        methods.add("sftp");
        methods.add("file");
        
        Properties properties = new Properties();
        properties.put("method", "file");
        properties.put("dateformat", "yyyyMMddHH");
        properties.put("local.delete", false);
        properties.put("remote.delete", false);
        properties.put("filename.pattern", "previsao_\\d{10}\\.txt");
        
        Set<String> parameters = new HashSet();        
        parameters.add("--method");
        parameters.add("--properties");
        parameters.add("--files");
        parameters.add("--local-delete");
        parameters.add("--remote-delete");
        parameters.add("--dateformat");
        parameters.add("-- filename-pattern");                
        
        List<Path> paths = new ArrayList<>();
        
        List<String> _args = new ArrayList(Arrays.asList(args));
        
        for (String parameter : parameters) 
        {
            if(_args.contains(parameter))
            {
                switch (parameter.toLowerCase())
                {
                    case "--method":
                        String method = _args.get(_args.indexOf(parameter) + 1);
                        if(!methods.contains(method))
                        {
                            logger.error("The specified method " + method + " is unknown!");
                            logger.error("The default method " + properties.getProperty("method") + " will be used.");
                        }
                        else 
                        {
                            properties.setProperty("method", method.toLowerCase());
                        }
                        break;
                    case "--files":
                        for(int i = (_args.indexOf(parameter) + 1); i < _args.size(); i++)
                        {
                            if(parameters.contains(_args.get(i)))
                            {
                                break;
                            }
                            Path path = Paths.get(_args.get(i));
                            if(cliente.isValid(path))
                            {
                                paths.add(path);
                            }
                        }                                                
                        break;
                    case "--properties":
                        Path file = Paths.get(_args.get(_args.indexOf(parameter) + 1));
                        if(cliente.isValid(file))
                        {
                            Properties props = new Properties();
                            try {
                                props.load(new FileInputStream(file.toFile()));
                            } catch (IOException ex) {
                                logger.error(FATAL, "Error when reading properties file", ex);
                            }
                            try {
                                if(cliente.isValid(props))
                                {
                                    properties.putAll(props);                            
                                }
                            } catch (PropertiesException ex) {
                                logger.error(FATAL, "Properties file is not valid.", ex);
                            }
                        }
                    case "--local-delete":
                        properties.put("local.delete", Boolean.parseBoolean(_args.get(_args.indexOf(parameter) + 1)));
                        break;
                    case "--remote-delete":
                        properties.put("remote.delete", Boolean.parseBoolean(_args.get(_args.indexOf(parameter) + 1)));
                        break;                    
                    case "--dateformat":
                        properties.put("dateformat", _args.get(_args.indexOf(parameter) + 1));
                        break;
                    case "--filename-patern":
                        properties.put("filename.pattern", _args.get(_args.indexOf(parameter) + 1));
                }
            }
        }               
        
        DateFormat dateFormat = new SimpleDateFormat(properties.getProperty("dateformat", "yyyyMMddHH"));         
        
        /**
         * Se metodo = file, os arquivos já estão em List<Path> paths
         * Se metodo = sftp, processe o download e adicione os arquivos à List<Path> paths
         */
        if(properties.getProperty("method").equals("sftp"))
        {
            paths = cliente.processaFTP(properties);
        }              
        
        /**
         * Ordena os arquivos pela data de criação contida no filename
         */
        Collections.sort(paths, DATE_ORDER);        
        
        EntityManagerWrapper wrapper = new EntityManagerWrapper("cliente-cemaden");
        
        AgenciaRepository aRepository = new AgenciaRepository(wrapper.getEntityManager());
        Agencia agencia = aRepository.find("Cemaden");
        
        Parser parser = new Parser();
        Pattern p = Pattern.compile("\\d+");
        
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        calendar.set(Calendar.MILLISECOND, 0);
        Date now = calendar.getTime();
        
        for(Path path : paths) {       

            String filename = path.getFileName().toString();
            
            if(!filename.matches(properties.getProperty("filename.pattern")))
            {
                logger.info(String.format("The filename %s does not match the expected value", filename));
                continue;
            }                                                            
                        
            Matcher m = p.matcher(filename);
            
            String date = "";                        
            
            while(m.find()){
                date = m.group();
            }
            
            if(date.isEmpty())
            {
                logger.info("The filename date does not match the expected value or does not exists.");
            }  
            else
            {
            
                Date data = null;
                try {
                    data = dateFormat.parse(date);
                } catch (ParseException ex) {
                    logger.error("Error when parsing date to java.utilDate object: " + date, ex);
                }

                /**
                 * Cria uma List de PrevisaoPrecipitacao e um objeto AquisicaoPrecipitacao.
                 */

                List<PrevisaoPrecipitacao> previsoes = new ArrayList();

                AquisicaoPrecipitacao aquisicao = new AquisicaoPrecipitacao();
                aquisicao.setDatahoraArquivo(data);                
                aquisicao.setAquisicao(now);
                aquisicao.setAtualizacao(now);
                aquisicao.setAgencia(agencia);

                /**
                 * Lê as previsões dos arquivos
                 */

                try {                                        
                    previsoes.addAll(parser.parse(path, properties, aquisicao, wrapper.getEntityManager()));
                } catch (IOException ex) {
                     logger.error("Error when trying to read file " + path.toString(), ex);
                } catch (ParseException ex) {
                    logger.error("Error when parsing date", ex);
                } catch (ObjectNotFoundException ex) {
                    logger.error("Municipio not found.", ex);
                }

                /**
                 * Relaciona aquisição às previsões para que sejam salvos todos de uma vez
                 */                                                                
                aquisicao.setPrevisoes(previsoes);

                if(!aquisicao.exists(wrapper.getEntityManager())) {                                

                    try {
                        aquisicao.save(wrapper.getEntityManager());
                    }
                    catch (NullPointerException ex) {
                        logger.error("Error when trying to persist data: connection to database is closed", ex);
                    }
                }
                else {
                    
                    AquisicaoPrecipitacaoDAO apDao = new AquisicaoPrecipitacaoDAO(wrapper.getEntityManager());
                    
                    AquisicaoPrecipitacao _aquisicao = apDao.find(aquisicao.getDatahoraArquivo(), aquisicao.getAgencia());
                    
                    List<PrevisaoPrecipitacao> _previsoes = new ArrayList<>();

                    PrevisaoPrecipitacaoDAO ppDao = new PrevisaoPrecipitacaoDAO(wrapper.getEntityManager());

                    for(PrevisaoPrecipitacao previsao : previsoes) 
                    {
                        // Se o campo valor da previsão atual for maior ou igual a zero, prossiga para atualizar o valor
                        if(previsao.getValor().compareTo(BigDecimal.ZERO) >= 0) {

                            PrevisaoPrecipitacao _previsao = ppDao.find(_aquisicao, previsao.getDatahora(), previsao.getLatitude(), previsao.getLongitude());

                            // Verifica a existência da previsão no banco de dados; se existir e o campo valor da previsão aferida e a existente diferirem, atribui o novo valor.
                            if(_previsao != null) 
                            {                            
                                if(_previsao.getValor().compareTo(previsao.getValor()) != 0) 
                                {
                                    _previsao.setValor(previsao.getValor());
                                    _previsoes.add(_previsao);
                                }
                            }
                            // se a previsão não existe no banco de dados, essa é relacionada com a AquisicaoPrecipitacao gerenciada.
                            else 
                            {
                                previsao.setAquisicao(_aquisicao);
                                _previsoes.add(previsao);
                            }
                        }
                    }
                    
                    if(_previsoes.size() > 0) 
                    {
                        _aquisicao.setPrevisoes(_previsoes);
                        _aquisicao.setAtualizacao(now);
                        _aquisicao.update(wrapper.getEntityManager());
                    }
                }
            }
                        
            if((Boolean)properties.get("local.delete"))
            {
                try {
                    Files.deleteIfExists(path);
                } catch (IOException ex) {
                    logger.error("Error when trying to delete file " + path.toAbsolutePath(), ex);
                }
            }            
        }
        
        logger.debug("Cliente finalizado.");
    }
    
    private List<Path> processaFTP(Properties properties){
        
        logger.debug("Processando FTP");
        
        List<Path> paths = new ArrayList();
            
        Path tmp = Paths.get(properties.getProperty("sftp.tmpdir", "."));        
        
        if(Files.notExists(tmp)){
            try {
                logger.info("Directory not exists: " + tmp.toAbsolutePath());   
                Files.createDirectory(tmp);
            } catch (IOException ex) {
                logger.error(FATAL, "The tmp directory can't be created: " + tmp.toAbsolutePath().toString(), ex);
                return null;
            }
        }
        if(!Files.isWritable(tmp)){
            logger.error(FATAL, "Is needed permission to write on tmp directory: " + tmp.toAbsolutePath().toString());
            return null;
        }

        FTPData data = new FTPData(
                properties.getProperty("sftp.host"), 
                properties.getProperty("sftp.user"), 
                properties.getProperty("sftp.password"), 
                Integer.parseInt(properties.getProperty("sftp.port", "22"))
        );
        FTPClient ftp = new FTPClient(data);
        
        try {
            logger.debug("Conectando ao servidor FTP");
            ftp.connect();
        } catch (JSchException ex) {
            logger.error("Error when trying to connect to sftp server.", ex);
        }

        try {
            paths.addAll(ftp.get(properties.getProperty("sftp.directory", "."), "previsao_*.txt", tmp));
        } catch (JSchException ex) {
            logger.error("SSH client error.", ex);
        } catch (SftpException ex) {
            logger.error("SFTP client error.", ex);
        }        
        
        
        if((Boolean)properties.get("remote.delete"))
        {
            try {
                ftp.delete(properties.getProperty("sftp.directory"), "previsao_*.txt");
            } catch (SftpException ex) {
                logger.error("Error when trying to delete files on SFTP server.", ex);
            }
        }
        logger.debug("Desconectando do servidor FTP");
        ftp.disconnect();
        
        return paths;
    }    
    
    private Boolean isValid(Properties properties) throws PropertiesException {        
        
        if(!properties.containsKey("sftp.host".toString()) 
                || !properties.containsKey("sftp.user") 
                || !properties.containsKey("sftp.password") 
                || !properties.containsKey("sftp.port")
                || !properties.containsKey("sftp.directory")
                || !properties.containsKey("sftp.tmpdir")
        ){
            throw new PropertiesException("Key not found. These keys have to be in properties file: {sftp.host, sftp.user, sftp.password, sftp.port, sftp.directory, sftp.tmpdir}");
        }
        
        if( properties.getProperty("sftp.host").trim().isEmpty() || properties.getProperty("sftp.user").trim().isEmpty() || properties.getProperty("sftp.password").trim().isEmpty() )
        {
            throw new PropertiesException("Empty property. These properties can not be empty: sftp.host, sftp.user, sftp.password");
        }
        
        return true;
    }
    
    private Boolean isValid(Path path) {        
        
        if(Files.exists(path)) 
        {
            if(!Files.isDirectory(path)) 
            {
                if(Files.isReadable(path)) 
                {
                    return true;
                }
                else 
                {
                    logger.error("The specified file can't ne read.");
                    return false;
                }
            }
            else 
            {
                logger.info("The specified path is a directory: " + path.toString());
                return false;
            }
        }
        else 
        {
            logger.info("File not found " + path.toString());
            return false;
        }
    }    

/* EOF */
}